"""BLE Diagnostic Service.

Provides BLE interface for diagnostic operations including remote diagnosis,
status monitoring, and solution application.
"""

import asyncio
import json
import logging
import struct
import time
from typing import Dict, List, Optional, Any, Callable
from enum import Enum
from datetime import datetime

from ..protocol import BLEProtocol, MessageType
from ..notification import NotificationManager
from ...diagnostics import (
    DiagnosticEngine, DiagnosticSession, DiagnosticStatus,
    DetectedProblem, ProblemType, SeverityLevel,
    ReportGenerator, ReportFormat
)

logger = logging.getLogger(__name__)

# Service and Characteristic UUIDs
DIAGNOSTIC_SERVICE_UUID = "0000FF70-0000-1000-8000-00805F9B34FB"
DIAGNOSTIC_CONTROL_UUID = "0000FF71-0000-1000-8000-00805F9B34FB"    # Write
DIAGNOSTIC_STATUS_UUID = "0000FF72-0000-1000-8000-00805F9B34FB"     # Read, Notify
DIAGNOSTIC_REPORT_UUID = "0000FF73-0000-1000-8000-00805F9B34FB"     # Read
AUTO_FIX_UUID = "0000FF74-0000-1000-8000-00805F9B34FB"             # Write, Notify
PROBLEM_LIST_UUID = "0000FF75-0000-1000-8000-00805F9B34FB"         # Read
RECOMMENDATION_UUID = "0000FF76-0000-1000-8000-00805F9B34FB"       # Read


class DiagnosticCommand(Enum):
    """Diagnostic control commands."""
    START_DIAGNOSIS = 0x01          # Start new diagnostic session
    STOP_DIAGNOSIS = 0x02           # Stop current session
    RESTART_DIAGNOSIS = 0x03        # Restart diagnosis
    GET_STATUS = 0x04              # Get current status
    GET_REPORT = 0x05              # Get diagnostic report
    APPLY_RECOMMENDATION = 0x06     # Apply solution recommendation
    CANCEL_SESSION = 0x07          # Cancel session
    LIST_PROBLEMS = 0x08           # List detected problems
    GET_RECOMMENDATIONS = 0x09     # Get recommendations
    SET_PRIORITY = 0x0A            # Set session priority
    TRIGGER_DETECTOR = 0x0B        # Trigger specific detector


class AutoFixCommand(Enum):
    """Auto-fix control commands."""
    APPLY_FIX = 0x01               # Apply automatic fix
    ROLLBACK_FIX = 0x02            # Rollback previous fix
    GET_FIX_STATUS = 0x03          # Get fix status
    APPROVE_FIX = 0x04             # Approve pending fix
    REJECT_FIX = 0x05              # Reject pending fix


class DiagnosticNotification(Enum):
    """Diagnostic notification types."""
    STATUS_CHANGE = 0x01           # Session status changed
    PROBLEM_DETECTED = 0x02        # New problem detected
    ANALYSIS_COMPLETE = 0x03       # Analysis completed
    RECOMMENDATION_READY = 0x04    # Recommendations available
    FIX_APPLIED = 0x05            # Fix has been applied
    FIX_FAILED = 0x06             # Fix failed
    SESSION_COMPLETE = 0x07        # Session completed
    ERROR_OCCURRED = 0x08         # Error in diagnosis


class DiagnosticService:
    """BLE Diagnostic Service implementation."""
    
    def __init__(self, diagnostic_engine: DiagnosticEngine, 
                 report_generator: ReportGenerator,
                 notification_manager: Optional[NotificationManager] = None):
        """Initialize diagnostic service.
        
        Args:
            diagnostic_engine: Diagnostic engine instance
            report_generator: Report generator instance
            notification_manager: Optional notification manager
        """
        self.diagnostic_engine = diagnostic_engine
        self.report_generator = report_generator
        self.notification_manager = notification_manager
        
        # Service state
        self.active_sessions: Dict[str, str] = {}  # device_id -> session_id
        self.cached_reports: Dict[str, bytes] = {}  # session_id -> report_data
        self.pending_fixes: Dict[str, Dict[str, Any]] = {}  # session_id -> fix_info
        
        # Notification callbacks
        self.notify_callbacks: Dict[str, Callable] = {}
        
        # Statistics
        self.statistics = {
            "sessions_started": 0,
            "sessions_completed": 0,
            "problems_detected": 0,
            "recommendations_applied": 0,
            "auto_fixes_performed": 0,
            "reports_generated": 0,
            "errors": 0
        }
        
        # Configuration
        self.max_report_cache_size = 5 * 1024 * 1024  # 5MB
        self.report_cache_ttl = 3600  # 1 hour
        self.auto_diagnosis_enabled = False
        self.auto_fix_enabled = False
        
        logger.info("DiagnosticService initialized")
    
    async def start_service(self):
        """Start the diagnostic service."""
        # Start diagnostic engine
        await self.diagnostic_engine.start()
        
        # Start auto-diagnosis if enabled
        if self.auto_diagnosis_enabled:
            asyncio.create_task(self._auto_diagnosis_loop())
        
        logger.info("DiagnosticService started")
    
    async def stop_service(self):
        """Stop the diagnostic service."""
        # Cancel all active sessions
        for device_id, session_id in list(self.active_sessions.items()):
            await self.diagnostic_engine.cancel_session(session_id, "Service shutdown")
        
        # Stop diagnostic engine
        await self.diagnostic_engine.stop()
        
        # Clear caches
        self.cached_reports.clear()
        self.pending_fixes.clear()
        
        logger.info("DiagnosticService stopped")
    
    async def handle_write_diagnostic_control(self, device_id: str, value: bytes) -> bool:
        """Handle diagnostic control characteristic write.
        
        Args:
            device_id: Device identifier
            value: Command data
            
        Returns:
            True if command was handled successfully
        """
        try:
            if not value or len(value) < 1:
                return False
            
            command = DiagnosticCommand(value[0])
            params = value[1:] if len(value) > 1 else b''
            
            if command == DiagnosticCommand.START_DIAGNOSIS:
                return await self._handle_start_diagnosis(device_id, params)
            
            elif command == DiagnosticCommand.STOP_DIAGNOSIS:
                return await self._handle_stop_diagnosis(device_id)
            
            elif command == DiagnosticCommand.RESTART_DIAGNOSIS:
                return await self._handle_restart_diagnosis(device_id, params)
            
            elif command == DiagnosticCommand.GET_STATUS:
                return await self._handle_get_status(device_id)
            
            elif command == DiagnosticCommand.GET_REPORT:
                return await self._handle_get_report(device_id, params)
            
            elif command == DiagnosticCommand.APPLY_RECOMMENDATION:
                return await self._handle_apply_recommendation(device_id, params)
            
            elif command == DiagnosticCommand.CANCEL_SESSION:
                return await self._handle_cancel_session(device_id)
            
            elif command == DiagnosticCommand.LIST_PROBLEMS:
                return await self._handle_list_problems(device_id)
            
            elif command == DiagnosticCommand.GET_RECOMMENDATIONS:
                return await self._handle_get_recommendations(device_id)
            
            elif command == DiagnosticCommand.SET_PRIORITY:
                return await self._handle_set_priority(device_id, params)
            
            elif command == DiagnosticCommand.TRIGGER_DETECTOR:
                return await self._handle_trigger_detector(device_id, params)
            
            else:
                logger.warning(f"Unknown diagnostic command: {command}")
                return False
            
        except Exception as e:
            logger.error(f"Error handling diagnostic control: {e}")
            self.statistics["errors"] += 1
            await self._send_notification(
                device_id,
                DiagnosticNotification.ERROR_OCCURRED,
                {"error": str(e)}
            )
            return False
    
    async def handle_read_diagnostic_status(self, device_id: str) -> bytes:
        """Handle diagnostic status characteristic read.
        
        Args:
            device_id: Device identifier
            
        Returns:
            Status data bytes
        """
        try:
            session_id = self.active_sessions.get(device_id)
            
            if session_id:
                session = await self.diagnostic_engine.get_session_status(session_id)
                if session:
                    # Format: [status(1), problems(1), recommendations(1), confidence(1), duration(2), flags(1)]
                    status_data = struct.pack(
                        '>BBBBHB',
                        self._encode_status(session.status),
                        min(255, len(session.detected_problems)),
                        min(255, len(session.recommendations)),
                        int(session.confidence_score * 100),
                        min(65535, session.actual_duration),
                        self._encode_flags(session)
                    )
                    return status_data
            
            # No active session
            return struct.pack('>BBBBHB', 0, 0, 0, 0, 0, 0)
            
        except Exception as e:
            logger.error(f"Error reading diagnostic status: {e}")
            return b'\x00' * 7
    
    async def handle_read_diagnostic_report(self, device_id: str) -> bytes:
        """Handle diagnostic report characteristic read.
        
        Args:
            device_id: Device identifier
            
        Returns:
            Report data bytes (may be chunked)
        """
        try:
            session_id = self.active_sessions.get(device_id)
            if not session_id:
                return b''
            
            # Check cache first
            if session_id in self.cached_reports:
                return self.cached_reports[session_id]
            
            # Generate report
            report_data = await self.diagnostic_engine.get_session_report(
                session_id, 
                format="json"
            )
            
            if report_data:
                # Cache report (with size limit)
                if len(report_data) <= self.max_report_cache_size:
                    self.cached_reports[session_id] = report_data
                    # Schedule cache cleanup
                    asyncio.create_task(
                        self._cleanup_report_cache(session_id, self.report_cache_ttl)
                    )
                
                self.statistics["reports_generated"] += 1
                return report_data
            
            return b''
            
        except Exception as e:
            logger.error(f"Error reading diagnostic report: {e}")
            return b''
    
    async def handle_write_auto_fix(self, device_id: str, value: bytes) -> bool:
        """Handle auto-fix characteristic write.
        
        Args:
            device_id: Device identifier
            value: Command data
            
        Returns:
            True if command was handled successfully
        """
        try:
            if not value or len(value) < 1:
                return False
            
            command = AutoFixCommand(value[0])
            params = value[1:] if len(value) > 1 else b''
            
            session_id = self.active_sessions.get(device_id)
            if not session_id:
                return False
            
            if command == AutoFixCommand.APPLY_FIX:
                return await self._handle_apply_fix(device_id, session_id, params)
            
            elif command == AutoFixCommand.ROLLBACK_FIX:
                return await self._handle_rollback_fix(device_id, session_id, params)
            
            elif command == AutoFixCommand.GET_FIX_STATUS:
                return await self._handle_get_fix_status(device_id, session_id)
            
            elif command == AutoFixCommand.APPROVE_FIX:
                return await self._handle_approve_fix(device_id, session_id, params)
            
            elif command == AutoFixCommand.REJECT_FIX:
                return await self._handle_reject_fix(device_id, session_id, params)
            
            else:
                logger.warning(f"Unknown auto-fix command: {command}")
                return False
            
        except Exception as e:
            logger.error(f"Error handling auto-fix command: {e}")
            self.statistics["errors"] += 1
            return False
    
    async def handle_read_problem_list(self, device_id: str) -> bytes:
        """Handle problem list characteristic read.
        
        Args:
            device_id: Device identifier
            
        Returns:
            Problem list data
        """
        try:
            session_id = self.active_sessions.get(device_id)
            if not session_id:
                return b''
            
            session = await self.diagnostic_engine.get_session_status(session_id)
            if not session or not session.detected_problems:
                return b''
            
            # Format problem list as JSON
            problems = []
            for problem in session.detected_problems[:20]:  # Limit to 20 problems
                problems.append({
                    "id": problem.problem_id[:8],  # Shortened ID
                    "type": problem.problem_type.value,
                    "severity": problem.severity.value,
                    "title": problem.title,
                    "confidence": problem.confidence
                })
            
            return json.dumps(problems).encode('utf-8')
            
        except Exception as e:
            logger.error(f"Error reading problem list: {e}")
            return b''
    
    async def handle_read_recommendations(self, device_id: str) -> bytes:
        """Handle recommendations characteristic read.
        
        Args:
            device_id: Device identifier
            
        Returns:
            Recommendations data
        """
        try:
            session_id = self.active_sessions.get(device_id)
            if not session_id:
                return b''
            
            session = await self.diagnostic_engine.get_session_status(session_id)
            if not session or not session.recommendations:
                return b''
            
            # Format recommendations as JSON
            recommendations = []
            for rec in session.recommendations[:10]:  # Limit to 10 recommendations
                recommendations.append({
                    "id": rec.recommendation_id[:8],  # Shortened ID
                    "title": rec.title,
                    "type": rec.solution_type,
                    "confidence": rec.confidence,
                    "automated": rec.is_automated,
                    "estimated_time": rec.estimated_time,
                    "risk": rec.risk_level
                })
            
            return json.dumps(recommendations).encode('utf-8')
            
        except Exception as e:
            logger.error(f"Error reading recommendations: {e}")
            return b''
    
    async def _handle_start_diagnosis(self, device_id: str, params: bytes) -> bool:
        """Handle start diagnosis command."""
        try:
            # Parse parameters (if provided)
            problem_description = "General diagnosis"
            tags = []
            
            if params:
                try:
                    param_data = json.loads(params.decode('utf-8'))
                    problem_description = param_data.get("description", problem_description)
                    tags = param_data.get("tags", [])
                except:
                    # Use raw string if not JSON
                    problem_description = params.decode('utf-8', errors='ignore')
            
            # Check if session already exists
            if device_id in self.active_sessions:
                await self._handle_stop_diagnosis(device_id)
            
            # Start new session
            session_id = await self.diagnostic_engine.start_diagnostic_session(
                device_id=device_id,
                problem_description=problem_description,
                tags=tags
            )
            
            self.active_sessions[device_id] = session_id
            self.statistics["sessions_started"] += 1
            
            # Monitor session progress
            asyncio.create_task(self._monitor_session(device_id, session_id))
            
            # Send notification
            await self._send_notification(
                device_id,
                DiagnosticNotification.STATUS_CHANGE,
                {"status": "started", "session_id": session_id}
            )
            
            logger.info(f"Started diagnostic session {session_id} for device {device_id}")
            return True
            
        except Exception as e:
            logger.error(f"Failed to start diagnosis: {e}")
            return False
    
    async def _handle_stop_diagnosis(self, device_id: str) -> bool:
        """Handle stop diagnosis command."""
        try:
            session_id = self.active_sessions.get(device_id)
            if not session_id:
                return False
            
            # Cancel session
            await self.diagnostic_engine.cancel_session(session_id, "Stopped by user")
            
            # Cleanup
            del self.active_sessions[device_id]
            if session_id in self.cached_reports:
                del self.cached_reports[session_id]
            if session_id in self.pending_fixes:
                del self.pending_fixes[session_id]
            
            # Send notification
            await self._send_notification(
                device_id,
                DiagnosticNotification.STATUS_CHANGE,
                {"status": "stopped", "session_id": session_id}
            )
            
            logger.info(f"Stopped diagnostic session {session_id}")
            return True
            
        except Exception as e:
            logger.error(f"Failed to stop diagnosis: {e}")
            return False
    
    async def _handle_restart_diagnosis(self, device_id: str, params: bytes) -> bool:
        """Handle restart diagnosis command."""
        # Stop current session
        await self._handle_stop_diagnosis(device_id)
        
        # Start new session
        return await self._handle_start_diagnosis(device_id, params)
    
    async def _handle_get_status(self, device_id: str) -> bool:
        """Handle get status command."""
        # Status is returned through read characteristic
        # Send notification to trigger read
        session_id = self.active_sessions.get(device_id)
        if session_id:
            session = await self.diagnostic_engine.get_session_status(session_id)
            if session:
                await self._send_notification(
                    device_id,
                    DiagnosticNotification.STATUS_CHANGE,
                    {
                        "status": session.status.value,
                        "problems": len(session.detected_problems),
                        "recommendations": len(session.recommendations)
                    }
                )
        return True
    
    async def _handle_get_report(self, device_id: str, params: bytes) -> bool:
        """Handle get report command."""
        try:
            session_id = self.active_sessions.get(device_id)
            if not session_id:
                return False
            
            # Parse format parameter
            report_format = "json"
            if params:
                try:
                    format_byte = params[0]
                    format_map = {
                        0: "json",
                        1: "html",
                        2: "text",
                        3: "markdown",
                        4: "xml"
                    }
                    report_format = format_map.get(format_byte, "json")
                except:
                    pass
            
            # Generate report
            report_data = await self.report_generator.generate_report(
                await self.diagnostic_engine.get_session_status(session_id),
                format=report_format
            )
            
            if report_data:
                # Cache report
                self.cached_reports[session_id] = report_data
                self.statistics["reports_generated"] += 1
                
                # Send notification that report is ready
                await self._send_notification(
                    device_id,
                    DiagnosticNotification.STATUS_CHANGE,
                    {"report_ready": True, "size": len(report_data)}
                )
                
                return True
            
            return False
            
        except Exception as e:
            logger.error(f"Failed to generate report: {e}")
            return False
    
    async def _handle_apply_recommendation(self, device_id: str, params: bytes) -> bool:
        """Handle apply recommendation command."""
        try:
            session_id = self.active_sessions.get(device_id)
            if not session_id or not params:
                return False
            
            # Parse recommendation ID
            recommendation_id = params.decode('utf-8', errors='ignore')
            
            # Apply recommendation
            success = await self.diagnostic_engine.apply_recommendation(
                session_id,
                recommendation_id
            )
            
            if success:
                self.statistics["recommendations_applied"] += 1
                
                # Send notification
                await self._send_notification(
                    device_id,
                    DiagnosticNotification.FIX_APPLIED,
                    {"recommendation_id": recommendation_id, "success": True}
                )
            else:
                await self._send_notification(
                    device_id,
                    DiagnosticNotification.FIX_FAILED,
                    {"recommendation_id": recommendation_id, "success": False}
                )
            
            return success
            
        except Exception as e:
            logger.error(f"Failed to apply recommendation: {e}")
            return False
    
    async def _handle_cancel_session(self, device_id: str) -> bool:
        """Handle cancel session command."""
        return await self._handle_stop_diagnosis(device_id)
    
    async def _handle_list_problems(self, device_id: str) -> bool:
        """Handle list problems command."""
        # Problems are returned through read characteristic
        # Send notification to trigger read
        session_id = self.active_sessions.get(device_id)
        if session_id:
            session = await self.diagnostic_engine.get_session_status(session_id)
            if session and session.detected_problems:
                self.statistics["problems_detected"] = len(session.detected_problems)
                await self._send_notification(
                    device_id,
                    DiagnosticNotification.PROBLEM_DETECTED,
                    {"count": len(session.detected_problems)}
                )
        return True
    
    async def _handle_get_recommendations(self, device_id: str) -> bool:
        """Handle get recommendations command."""
        # Recommendations are returned through read characteristic
        # Send notification to trigger read
        session_id = self.active_sessions.get(device_id)
        if session_id:
            session = await self.diagnostic_engine.get_session_status(session_id)
            if session and session.recommendations:
                await self._send_notification(
                    device_id,
                    DiagnosticNotification.RECOMMENDATION_READY,
                    {"count": len(session.recommendations)}
                )
        return True
    
    async def _handle_set_priority(self, device_id: str, params: bytes) -> bool:
        """Handle set priority command."""
        try:
            if not params:
                return False
            
            session_id = self.active_sessions.get(device_id)
            if not session_id:
                return False
            
            priority = params[0]  # 1-10
            
            # Update session priority (would need to add this to DiagnosticEngine)
            # For now, just log it
            logger.info(f"Setting priority {priority} for session {session_id}")
            
            return True
            
        except Exception as e:
            logger.error(f"Failed to set priority: {e}")
            return False
    
    async def _handle_trigger_detector(self, device_id: str, params: bytes) -> bool:
        """Handle trigger detector command."""
        try:
            if not params:
                return False
            
            detector_name = params.decode('utf-8', errors='ignore')
            
            # Trigger specific detector (would need to add this to DiagnosticEngine)
            # For now, just log it
            logger.info(f"Triggering detector {detector_name} for device {device_id}")
            
            return True
            
        except Exception as e:
            logger.error(f"Failed to trigger detector: {e}")
            return False
    
    async def _handle_apply_fix(self, device_id: str, session_id: str, params: bytes) -> bool:
        """Handle apply fix command."""
        try:
            if not params:
                return False
            
            fix_id = params.decode('utf-8', errors='ignore')
            
            # Apply the fix
            success = await self.diagnostic_engine.apply_recommendation(session_id, fix_id)
            
            if success:
                self.statistics["auto_fixes_performed"] += 1
                await self._send_notification(
                    device_id,
                    DiagnosticNotification.FIX_APPLIED,
                    {"fix_id": fix_id}
                )
            else:
                await self._send_notification(
                    device_id,
                    DiagnosticNotification.FIX_FAILED,
                    {"fix_id": fix_id}
                )
            
            return success
            
        except Exception as e:
            logger.error(f"Failed to apply fix: {e}")
            return False
    
    async def _handle_rollback_fix(self, device_id: str, session_id: str, params: bytes) -> bool:
        """Handle rollback fix command."""
        # Rollback functionality would need to be implemented in DiagnosticEngine
        logger.info(f"Rollback requested for session {session_id}")
        return True
    
    async def _handle_get_fix_status(self, device_id: str, session_id: str) -> bool:
        """Handle get fix status command."""
        # Send current fix status
        fix_info = self.pending_fixes.get(session_id, {})
        await self._send_notification(
            device_id,
            DiagnosticNotification.STATUS_CHANGE,
            {"fix_status": fix_info}
        )
        return True
    
    async def _handle_approve_fix(self, device_id: str, session_id: str, params: bytes) -> bool:
        """Handle approve fix command."""
        fix_info = self.pending_fixes.get(session_id)
        if fix_info and fix_info.get("requires_approval"):
            # Apply the approved fix
            return await self._handle_apply_fix(device_id, session_id, params)
        return False
    
    async def _handle_reject_fix(self, device_id: str, session_id: str, params: bytes) -> bool:
        """Handle reject fix command."""
        if session_id in self.pending_fixes:
            del self.pending_fixes[session_id]
            await self._send_notification(
                device_id,
                DiagnosticNotification.STATUS_CHANGE,
                {"fix_rejected": True}
            )
        return True
    
    async def _monitor_session(self, device_id: str, session_id: str):
        """Monitor diagnostic session progress."""
        try:
            last_status = None
            last_problem_count = 0
            
            while session_id in self.active_sessions.values():
                await asyncio.sleep(2)  # Check every 2 seconds
                
                session = await self.diagnostic_engine.get_session_status(session_id)
                if not session:
                    break
                
                # Check for status change
                if session.status != last_status:
                    last_status = session.status
                    await self._send_notification(
                        device_id,
                        DiagnosticNotification.STATUS_CHANGE,
                        {"status": session.status.value}
                    )
                
                # Check for new problems
                if len(session.detected_problems) > last_problem_count:
                    last_problem_count = len(session.detected_problems)
                    await self._send_notification(
                        device_id,
                        DiagnosticNotification.PROBLEM_DETECTED,
                        {"count": last_problem_count}
                    )
                
                # Check for analysis completion
                if session.status == DiagnosticStatus.ANALYZING and session.analysis_results:
                    await self._send_notification(
                        device_id,
                        DiagnosticNotification.ANALYSIS_COMPLETE,
                        {"analyses": len(session.analysis_results)}
                    )
                
                # Check for recommendations
                if session.recommendations:
                    await self._send_notification(
                        device_id,
                        DiagnosticNotification.RECOMMENDATION_READY,
                        {"count": len(session.recommendations)}
                    )
                
                # Check for completion
                if session.status in [DiagnosticStatus.COMPLETED, DiagnosticStatus.FAILED, 
                                     DiagnosticStatus.CANCELLED]:
                    self.statistics["sessions_completed"] += 1
                    await self._send_notification(
                        device_id,
                        DiagnosticNotification.SESSION_COMPLETE,
                        {
                            "status": session.status.value,
                            "problems": len(session.detected_problems),
                            "recommendations": len(session.recommendations)
                        }
                    )
                    break
            
        except Exception as e:
            logger.error(f"Error monitoring session {session_id}: {e}")
    
    async def _auto_diagnosis_loop(self):
        """Auto-diagnosis background loop."""
        while self.auto_diagnosis_enabled:
            try:
                await asyncio.sleep(300)  # Check every 5 minutes
                
                # Implement auto-diagnosis logic here
                # This could check system health periodically
                # and start diagnostic sessions automatically
                
            except Exception as e:
                logger.error(f"Error in auto-diagnosis loop: {e}")
    
    async def _send_notification(self, device_id: str, 
                                notification_type: DiagnosticNotification,
                                data: Dict[str, Any]):
        """Send notification to device."""
        try:
            if self.notification_manager:
                # Format notification data
                notification_data = struct.pack('B', notification_type.value)
                
                # Add JSON payload
                if data:
                    json_data = json.dumps(data).encode('utf-8')
                    notification_data += json_data
                
                # Send through notification manager
                await self.notification_manager.notify(
                    device_id,
                    DIAGNOSTIC_STATUS_UUID,
                    notification_data
                )
            
            # Call registered callbacks
            callback = self.notify_callbacks.get(device_id)
            if callback:
                await callback(notification_type, data)
            
        except Exception as e:
            logger.error(f"Failed to send notification: {e}")
    
    async def _cleanup_report_cache(self, session_id: str, ttl: int):
        """Clean up cached report after TTL."""
        await asyncio.sleep(ttl)
        if session_id in self.cached_reports:
            del self.cached_reports[session_id]
            logger.debug(f"Cleaned up cached report for session {session_id}")
    
    def _encode_status(self, status: DiagnosticStatus) -> int:
        """Encode diagnostic status to byte value."""
        status_map = {
            DiagnosticStatus.INITIATED: 1,
            DiagnosticStatus.DETECTING: 2,
            DiagnosticStatus.ANALYZING: 3,
            DiagnosticStatus.RECOMMENDING: 4,
            DiagnosticStatus.APPLYING_FIX: 5,
            DiagnosticStatus.COMPLETED: 6,
            DiagnosticStatus.FAILED: 7,
            DiagnosticStatus.CANCELLED: 8,
            DiagnosticStatus.TIMEOUT: 9
        }
        return status_map.get(status, 0)
    
    def _encode_flags(self, session: DiagnosticSession) -> int:
        """Encode session flags to byte value."""
        flags = 0
        if session.auto_fix_count > 0:
            flags |= 0x01  # Has auto-fixes
        if session.manual_step_count > 0:
            flags |= 0x02  # Has manual steps
        if session.confidence_score > 0.8:
            flags |= 0x04  # High confidence
        if session.status == DiagnosticStatus.COMPLETED:
            flags |= 0x08  # Completed
        return flags
    
    def register_notification_callback(self, device_id: str, callback: Callable):
        """Register notification callback for device."""
        self.notify_callbacks[device_id] = callback
    
    def unregister_notification_callback(self, device_id: str):
        """Unregister notification callback for device."""
        if device_id in self.notify_callbacks:
            del self.notify_callbacks[device_id]
    
    def get_service_statistics(self) -> Dict[str, Any]:
        """Get service statistics."""
        return {
            "service_statistics": self.statistics.copy(),
            "active_sessions": len(self.active_sessions),
            "cached_reports": len(self.cached_reports),
            "pending_fixes": len(self.pending_fixes),
            "configuration": {
                "auto_diagnosis_enabled": self.auto_diagnosis_enabled,
                "auto_fix_enabled": self.auto_fix_enabled,
                "max_report_cache_size": self.max_report_cache_size,
                "report_cache_ttl": self.report_cache_ttl
            }
        }


# Example usage and testing
if __name__ == "__main__":
    import asyncio
    from ...diagnostics import DiagnosticEngine, ReportGenerator
    
    async def test_diagnostic_service():
        """Test diagnostic service."""
        # Create mock components
        diagnostic_config = {
            "engine": {
                "max_concurrent_sessions": 5,
                "session_timeout": 300
            }
        }
        
        report_config = {
            "report_generator": {
                "default_format": "json"
            }
        }
        
        diagnostic_engine = DiagnosticEngine(diagnostic_config)
        report_generator = ReportGenerator(report_config)
        
        # Create service
        service = DiagnosticService(diagnostic_engine, report_generator)
        
        # Start service
        await service.start_service()
        
        print("Testing diagnostic service...")
        
        # Test start diagnosis
        device_id = "test_device_001"
        start_command = struct.pack('B', DiagnosticCommand.START_DIAGNOSIS.value)
        start_params = json.dumps({"description": "Test diagnosis"}).encode('utf-8')
        success = await service.handle_write_diagnostic_control(
            device_id, 
            start_command + start_params
        )
        print(f"Start diagnosis: {success}")
        
        # Wait for diagnosis to progress
        await asyncio.sleep(2)
        
        # Test get status
        status_data = await service.handle_read_diagnostic_status(device_id)
        print(f"Status data: {status_data.hex()}")
        
        # Test get report
        get_report_command = struct.pack('BB', DiagnosticCommand.GET_REPORT.value, 0)
        success = await service.handle_write_diagnostic_control(device_id, get_report_command)
        print(f"Get report: {success}")
        
        # Read report
        report_data = await service.handle_read_diagnostic_report(device_id)
        if report_data:
            print(f"Report size: {len(report_data)} bytes")
        
        # Get statistics
        stats = service.get_service_statistics()
        print(f"Service statistics: {stats}")
        
        # Stop service
        await service.stop_service()
    
    # Run test
    asyncio.run(test_diagnostic_service())